Webpack_4-Guides

cc and abstract.
https://doc.webpack-china.org/guides/development/

2018.1.25 星期四 14:42

一 安装

1.1 前提条件

1.2 本地安装

1.3 全局安装

1.4 最新体验版本

二 起步

2.1 基本安装

2.2 创建一个bundle文件

2.3 模块

2.4 使用一个配置文件

2.5 NPM脚本

2.6 结论

三 管理资源

3.1 安装

3.2 加载CSS

3.3 加载字体

3.4 加载数据

3.5 全局资源

3.6 回退处理

3.7 下一章节指南

3.8 延伸阅读

四 管理输出

4.1 预先准备

4.2 设定HtmlWebpackPlugin

4.3 清理/dist文件夹

4.4 Manifest

4.5 结论

1

2

3

4

5

####

五 开发

5.1 使用source map

5.2 选择一个开发工具

5.3 使用观察模式

5.4 使用 webpack-dev-server

5.5 使用 Webpack-dev-middleware

5.6 调整文本编辑器

5.7 结论

六 模块热替换

6.1 启用 HMR

6.2 通过Node.js API

6.3 问题

6.4 HMR 修改样式表

6.5 其他代码和框架

七 Tree Shaking

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。

webpack 2 内置支持 ES2015 模块(别名 harmony modules),并能检测出未使用的模块导出。

7.1 添加一个通用模块

注意,上面的 unused harmony export square 注释。如果你看下面的代码,你会注意到 square 没有被导出,但是,它仍然被包含在 bundle 中。我们将在下一节中解决这个问题。

7.2 精简输出

UglifyJSPlugin

显然,现在整个 bundle 都已经被精简过,但是如果仔细观察,则不会看到 square 函数被引入,但会看到 cube 函数的修改版本(function r(e){return eee}n.a=r)

7.3 警告

请注意,webpack 本身并不会执行 tree-shaking。它需要依赖于像 UglifyJS 这样的第三方工具来执行实际的未引用代码(dead code)删除工作。有些情况下,tree-shaking 可能不会生效。例如,考虑以下模块:
。。
一般来说,当一个工具不能保证某些特定的代码路径(path)不会导致副作用(side-effects)时,即使你确信它不应该存在生成的 bundle 中,但这个代码仍然会保留。常见的情况有:从第三方模块中调用一个函数,webpack 和/或 压缩工具(minifier)无法检查此模块;从第三方模块导入的函数被重新导出,等等。

7.4 结论

为了学会使用 tree shaking,你必须……

  • 使用 ES2015 模块语法(即 import 和 export)。
  • 引入一个能够删除未引用代码(dead code)的压缩工具(minifier)(例如 UglifyJSPlugin)。

八 生产环境构建

8.1 配置

开发环境(development)和生产环境(production)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。

8.2 NPM Script

8.3 Minification

注意,虽然 UglifyJSPlugin 是代码压缩方面比较好的选择,但是还有一些其他可选择项。以下有几个同样很受欢迎的插件:

8.4 source map

我们鼓励你在生产环境中启用 source map,因为它们对调试源码(debug)和运行基准测试(benchmark tests)很有帮助。虽然有如此强大的功能,然而还是应该针对生成环境用途,选择一个构建快速的推荐配置(具体细节请查看 devtool)。对于本指南,我们将在生产环境中使用 source-map 选项,而不是我们在开发环境中用到的inline-source-map

避免在生产中使用 inline- 和 eval-,因为它们可以增加 bundle 大小,并降低整体性能。

8.5 指定环境

许多 library 将通过与 process.env.NODE_ENV 环境变量关联,以决定 library 中应该引用哪些内容。例如,当不处于生产环境中时,某些 library 为了使调试变得容易,可能会添加额外的日志记录(log)和测试(test)。其实,当使用 process.env.NODE_ENV === ‘production’ 时,一些 library 可能针对具体用户的环境进行代码优化,从而删除或添加一些重要代码。我们可以使用 webpack 内置的 DefinePlugin 为所有的依赖定义这个变量:

如果你正在使用像 react 这样的 library,那么在添加此 DefinePlugin 插件后,你应该看到 bundle 大小显著下降。还要注意,任何位于 /src 的本地代码都可以关联到 process.env.NODE_ENV 环境变量,所以以下检查也是有效的:

import { cube } from './math.js';
if (process.env.NODE_ENV !== 'production') {
    console.log('Looks like we are in development mode!');
}

8.6 Split CSS

8.7 CLI 替代选项

九 代码分离

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间

有三种常用的代码分离方法:

  • 入口起点:使用 entry 配置手动地分离代码。
  • 防止重复:使用 CommonsChunkPlugin 去重和分离 chunk。
  • 动态导入:通过模块的内联函数调用来分离代码。

    9.1 入口起点

    正如前面提到的,这种方法存在一些问题:
  1. 如果入口 chunks 之间包含重复的模块,那些重复模块都会被引入到各个 bundle 中。
  2. 这种方法不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码。
    以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在 ./src/index.js 中也引入过 lodash,这样就在两个 bundle 中造成重复引用。接着,我们通过使用 CommonsChunkPlugin 来移除重复的模块。

    9.2 防止重复

    CommonsChunkPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重复的 lodash 模块去除:

这里我们使用 CommonsChunkPlugin 之后,现在应该可以看出,index.bundle.js 中已经移除了重复的依赖模块。需要注意的是,CommonsChunkPlugin 插件将 lodash 分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了大小。执行 npm run build 查看效果:

以下是由社区提供的,一些对于代码分离很有帮助的插件和 loaders:

  • ExtractTextPlugin: 用于将 CSS 从主应用程序中分离。
  • bundle-loader: 用于分离代码和延迟加载生成的 bundle。
  • promise-loader: 类似于 bundle-loader ,但是使用的是 promises。
  • CommonsChunkPlugin 插件还可以通过使用显式的 vendor chunks 功能,从应用程序代码中分离 vendor 模块。

    9.3 动态导入

    $_MORE:和ES import,async有关

    9.4 bundle分析

    9.5 下一步

零 懒加载

实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块

0.1 示例

button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
    var print = module.default;
    print();
});

注意当调用 ES6 模块的 import() 方法(引入模块)时,必须指向模块的 .default 值,因为它才是 promise 被处理后返回的实际的 module 对象。

0.2 框架

许多框架和类库对于如何用它们自己的方式来实现(懒加载)都有自己的建议。这里有一些例子:

  • React: Code Splitting and Lazy Loading
    *Vue: Lazy Load in Vue using Webpack’s code splitting

一 缓存

1.1 输出文件的文件名

可以看到,bundle 的名称是它内容(通过 hash)的映射。如果我们不做修改,然后再次运行构建,我们的文件名按照期望,依然保持不变。然而,如果我们再次运行,可能会发现情况并非如此:

这也是因为 webpack 在入口 chunk 中,包含了某些样板(boilerplate),特别是 runtime 和 manifest。(译注:样板(boilerplate)指 webpack 运行时的引导代码)

1.2 提取模板

就像我们之前从代码分离了解到的,CommonsChunkPlugin 可以用于将模块分离到单独的文件中。然而 CommonsChunkPlugin 有一个较少有人知道的功能是,能够在每次修改后的构建结果中,将 webpack 的样板(boilerplate)和 manifest 提取出来。通过指定 entry 配置中未用到的名称,此插件会自动将我们需要的内容提取到单独的包中:

const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    plugins: [
        new CleanWebpackPlugin(['dist']),
        new HtmlWebpackPlugin({
            title: 'Caching'
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vender'
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'manifest'
        })
    ],
    output: {
        filename: '[name].[chunkhash].js',
        path: path.resolve(__dirname, 'dist')
    }
};

将第三方库(library)(例如 lodash 或 react)提取到单独的 vendor chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上步骤,利用客户端的长效缓存机制,可以通过命中缓存来消除请求,并减少向服务器获取资源,同时还能保证客户端代码和服务器端代码版本一致。这可以通过使用新的 entry(入口) 起点,以及再额外配置一个 CommonsChunkPlugin 实例的组合方式来实现

注意,引入顺序在这里很重要。CommonsChunkPlugin 的 ‘vendor’ 实例,必须在 ‘manifest’ 实例之前引入。

1.3 模块标识符

再次运行构建,然后我们期望的是,只有 main bundle 的 hash 发生变化,然而……

……我们可以看到这三个文件的 hash 都变化了。这是因为每个 module.id 会基于默认的解析顺序(resolve order)进行增量。也就是说,当解析顺序发生变化,ID 也会随之改变。因此,简要概括:

  • main bundle 会随着自身的新增内容的修改,而发生变化。
  • vendor bundle 会随着自身的 module.id 的修改,而发生变化。
  • manifest bundle 会因为当前包含一个新模块的引用,而发生变化。
    第一个和最后一个都是符合预期的行为 – 而 vendor 的 hash 发生变化是我们要修复的。幸运的是,可以使用两个插件来解决这个问题。第一个插件是 NamedModulesPlugin,将使用模块的路径,而不是数字标识符。虽然此插件有助于在开发过程中输出结果的可读性,然而执行时间会长一些。第二个选择是使用 HashedModuleIdsPlugin,推荐用于生产环境构建:

    1.4 结论

    1.5 译注

二 创建Library

2.1 创建一个library

2.2 基本配置

2.3 外部化lodash

2.4 外部扩展的限制

2.5 暴露library

2.6 最终步骤

$_EXCLUDE:没有创建库,所以无法配置;目前不创建库

三 Shimming

2018.2.9 星期五 18:30

webpack 编译器(compiler)能够识别遵循 ES2015 模块语法、CommonJS 或 AMD 规范编写的模块。然而,一些第三方的库(library)可能会引用一些全局依赖(例如 jQuery 中的 $)。这些库也可能创建一些需要被导出的全局变量。这些“不符合规范的模块”就是 shimming 发挥作用的地方。

我们不推荐使用全局的东西!在 webpack 背后的整个概念是让前端开发更加模块化。也就是说,需要编写具有良好的封闭性(well contained)、彼此隔离的模块,以及不要依赖于那些隐含的依赖模块(例如,全局变量)。请只在必要的时候才使用本文所述的这些特性。

shimming 另外一个使用场景就是,当你希望 polyfill 浏览器功能以支持更多用户时。在这种情况下,你可能只想要将这些 polyfills 提供给到需要修补(patch)的浏览器(也就是实现按需加载)。

下面的文章将向我们展示这两种用例。

3.1 shimming全局变量

plugins: [
 new webpack.ProvidePlugin({
    _: 'lodash',
    join: ['lodash', 'join']
 })

]
我们还可以使用 ProvidePlugin 暴露某个模块中单个导出值,只需通过一个“数组路径”进行配置

3.2 细粒度shimming

一些传统的模块依赖的 this 指向的是 window 对象
module: {
rules: [
{
test: require.resolve(‘index.js’),
use: ‘imports-loader?this=>window’
}
]
},
$_PS: shimming的第一种情况。如,bootstrap需要jQuey,也是这种方式

3.3 全局exports

你可能从来没有在自己的源码中做过这些事情,但是你也许遇到过一个老旧的库(library),和上面所展示的代码类似。在这个用例中,我们可以使用 exports-loader,将一个全局变量作为一个普通的模块来导出。例如,为了将 file 导出为 file 以及将 helpers.parse 导出为 parse,做如下调整:

module: {
     rules: [
       {
            test: require.resolve('globals.js'),
            use: 'exports-loader?file,parse=helpers.parse'
       }
     ]
},

$_PS: 还是第一种情况。比如jQuery1.11.3,或者其他老旧库,将一个全局变量作为一个普通的模块来导出

3.4 加载polyfills

有很多方法来载入 polyfills。例如,要引入 babel-polyfill 我们只需要如下操作:

npm install --save babel-polyfill

然后使用 import 将其添加到我们的主 bundle 文件:

请注意,我们没有将 import 绑定到变量。这是因为只需在基础代码(code base)之外,再额外执行 polyfills,这样我们就可以假定代码中已经具有某些原生功能。

polyfills 虽然是一种模块引入方式,但是并不推荐在主 bundle 中引入 polyfills,因为这不利于具备这些模块功能的现代浏览器用户,会使他们下载体积很大、但却不需要的脚本文件。

让我们把 import 放入一个新文件,并加入 whatwg-fetch polyfill:

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
var modernBrowser = (
'fetch' in window &&
'assign' in Object
);
if ( !modernBrowser ) {
var scriptElement = document.createElement('script');

scriptElement.async = false;
scriptElement.src = '/polyfills.bundle.js';
document.head.appendChild(scriptElement);
}
</script>

3.5 深度优化

babel-preset-env package 使用 browserslist 来转译那些你浏览器中不支持的特性。这里预设了 useBuiltIns 选项,默认值是 false,能将你的全局 babel-polyfill 导入方式,改进为更细粒度的 import 格式

import 'core-js/modules/es7.string.pad-start';
import 'core-js/modules/es7.string.pad-end';
import 'core-js/modules/web.timers';
import 'core-js/modules/web.immediate';
import 'core-js/modules/web.dom.iterable';

$_PS: 开发库的时候使用。项目中还有一些API需要调用,自定义的对象,全局对象的API就不可以使用了Array.prototype.forEach。开发中用上面babel-loader, useBuiltIns 设为true

3.6 Node内置

像 process 这种 Node 内置模块,能直接根据配置文件(configuration file)进行正确的 polyfills,且不需要任何特定的 loaders 或者 plugins。查看 node 配置页面获取更多信息。

3.7 其他工具

还有一些其他的工具能够帮助我们处理这些老旧的模块。

  1. script-loader 会在全局上下文中对代码进行取值,类似于通过一个 script 标签引入脚本。在这种模式下,每一个标准的库(library)都应该能正常运行。require, module 等的取值是 undefined。

    当使用 script-loader 时,模块将转化为字符串,然后添加到 bundle 中。它不会被 webpack 压缩,所以你应该选择一个 min 版本。同时,使用此 loader 将不会有 devtool 的支持。

  2. 这些老旧的模块如果没有 AMD/CommonJS 规范版本,但你也想将他们加入 dist 文件,你可以使用 noParse 来标识出这个模块。这样就能使 webpack 将引入这些模块,但是不进行转化(parse),以及不解析(resolve) require() 和 import 语句。这个实践将提升构建性能

    例如 ProvidePlugin,任何需要 AST 的功能,都无法正常运行。

  3. 最后,有一些模块支持不同的模块格式,比如 AMD 规范、CommonJS 规范和遗留模块(legacy)。在大多数情况下,他们首先检查define,然后使用一些古怪的代码来导出一些属性。在这些情况下,可以通过imports-loader设置 define=>false 来强制 CommonJS 路径。
    $_PS: 什么鬼,imports-loader之前不是用了吗

译者注:shim 是一个库(library),它将一个新的 API 引入到一个旧的环境中,而且仅靠旧的环境中已有的手段实现。polyfill 就是一个用在浏览器 API 上的 shim。我们通常的做法是先检查当前浏览器是否支持某个 API,如果不支持的话就加载对应的 polyfill。然后新旧浏览器就都可以使用这个 API 了。

$__PS: 下面这些plugin,loader,configuration 不是一个概念,注意使用

  1. ProvidePlugin:把模块作为应用程序中的一个全局变量。
  2. imports-loader: 模块需要某个依赖时使用,比如this指向window,bootstrap需要jQuery
  3. exports-loader:将一个全局变量作为一个普通的模块来导出,需要import
  4. expose-loader: 暴露一个全局变量,不需要import。注意和ProvidePlugin的区别:??ProvidePlugin可以暴露jQuery1.11.3的全局变量吗

    The expose loader adds modules to the global object. This is useful for debugging, or supporting libraries that depend on libraries in globals.

  5. script-loader:估计和expose差不多
  6. noParse:expose,script,还有exports的模块应该可以用module.noParse配置

    noParse:防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中不应该含有 import, require, define 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能。

  7. module.externals :有人说不符合webpack模块管理了??

    防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。
    例如,从 CDN 引入 jQuery,而不是把它打包:

  8. DllPlugin:??如果不用externals排除,用该方法剥离不经常改动的第三方依赖

    使用 DllPlugin 将更改不频繁的代码进行单独编译。这将改善引用程序的编译速度,即使它增加了构建过程的复杂性。
    DLLPlugin 和 DLLReferencePlugin 用某种方法实现了拆分 bundles,同时还大大提升了构建的速度。

  9. CommonsChunkPlugin:提取公共子模块,提供缓存。属于代码分离–防止重复。公共模块提出来,利用缓存,没有修改的部分,hashID不会更改

    CommonsChunkPlugin,是一个可选的用于建立一个独立文件(又称作 chunk)的功能,这个文件包括多个入口 chunk 的公共模块。通过将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存起来到缓存中供后续使用。这个带来速度上的提升,因为浏览器会迅速将公共的代码从缓存中取出来,而不是每次访问一个新页面时,再去加载一个更大的文件。

  10. ExtractTextPlugin:也属于代码分离,用于将 CSS 从主应用程序中分离
  11. UglifyJSPlugin:Tree Shaking–精简输出

2.9 20:46

四 TypeScript

4.1 基础安装

4.2 Loader

4.3 source map

4.4 使用第三方库

4.5 导入其他资源

4.6 构建性能

$_EXCLUDE:typescript语法没有用到

五 渐进式网络应用程序

5.1 现在我们并没有离线环境

5.2 天津Workbox

5.3 注册我们的Service Worker

5.4 结论

$_EXCLUDE:PWA,最后构建

六 迁移到新版本

$_EXCLUDE:没有迁移

七 使用环境变量

$_PROBLEM:没实际实例,看不到目的,所以操作不明

八 构建性能

8.1 常规

1 保持版本最新

2 Loaders

3 Bootstrap

4 解析

5 Dlls

6 Samller = Faster

7 Worker Pool

8 持久化缓存

9 自定义 plugins/loaders

8.2 Development

1 增量编译

2 在内存中编译

3 Devtool

4 避免在生产环境下才会用

5 最小化入口chunk

8.3 Production

1 多个编译时

2 Source Maps

8.4 工具相关问题

1 Babel

2 Typescript

3 Sass

九 开发-Vagrant

9.1 项目配置

9.2 启动服务器

9.3 配合nginx的高级用法

9.4 小结

$_EXCLUDE:不需要

零 管理依赖

0.1 带表达式的require语句

0.2 require.context

0.3 上下文模块

$_MORE:不明白

一 公共路径(Public Path)

webpack 提供一个非常有用的配置,该配置能帮助你为项目中的所有资源指定一个基础路径。它被称为公共路径(publicPath)。

1.1 示例

1 在构建项目时设置路径值

在开发模式中,我们通常有一个 assets/ 文件夹,它往往存放在和首页一个级别的目录下。这样是挺方便;但是如果在生产环境下,你想把这些静态文件统一使用CDN加载,那该怎么办?

想要解决这个问题,你可以使用有着悠久历史的环境变量。比如说,我们设置了一个名为 ASSET_PATH 的变量:

import webpack from 'webpack';
// 如果预先定义过环境变量,就将其赋值给`ASSET_PATH`变量,否则赋值为根目录
const ASSET_PATH = process.env.ASSET_PATH || '/';

export default {
    output: {
        publicPath: ASSET_PATH
    },

    plugins: [
        // 该插件帮助我们安心地使用环境变量
        new webpack.DefinePlugin({
            'process.env.ASSET_PATH': JSON.stringify(ASSET_PATH)
        })
    ]
};

2 即时设定路径值

另一个可能出现的情况是,我们需要即时设置公共路径。webpack 提供一个全局变量供你设置,它名叫__webpack_public_path__。所以在你的项目入口,你可以简单地设置如下:

__webpack_public_path__ = process.env.ASSET_PATH;

一切设置完成。因为我们已经在我们的配置项中使用了DefinePlugin, process.env.ASSET_PATH 就已经被定义了, 所以让我们能够安心地使用它了。

警告:请注意,如果你在入口文件中使用 ES6 模块导入,则在导入后对 webpack_public_path 进行赋值。在这种情况下,你必须将公共路径(public path)赋值移至自己的专属模块,然后将其导入到你的 entry.js 之上:

// entry.js
import './public-path';
import './app';

$_MORE:警告是什么意思??

二 集成(Intergrations)

2.1 NPM Scripts

2.2 Grunt

2.3 Gulp

2.4 Mocha

2.5 Karma

$_EXCLUDE:不需要集成,单独使用

knowledge is no pay,reward is kindness
0%